﻿using System;
using System.Threading;
using PoolKit.Commands;

namespace PoolKit
{
    /// <summary>
    /// TODO: throttle this (target of opportunity for next releases)
    /// </summary>
    /// <typeparam name="TResource"></typeparam>
    internal class CollectionMinimumCountEnforcer<TResource> : IDisposable
    {
        private readonly IResourceCollection<TResource> _resourceCollection;
        private readonly ICommandProcessor _commandProcessor;
        private bool _running;
        private readonly CancellationTokenSource _commandCancellationToken = new CancellationTokenSource();
        private readonly Semaphore _runningCommandsSemaphore;
        private readonly int _intendedCount;
        private readonly ResourceCreationStrategy<TResource> _resourceCreationStrategy;
        private readonly DisposableCookie _disposableCookie = new DisposableCookie(Resources.MonitorAlreadyDisposed);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resourceCollection"></param>
        /// <param name="commandProcessor"></param>
        /// <param name="intendedCount"></param>
        /// <param name="resourceCreationStrategy"></param>
        /// <param name="maximumEnforcerCommandsRunningCount"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public CollectionMinimumCountEnforcer(
            IResourceCollection<TResource> resourceCollection,
            ICommandProcessor commandProcessor,
            int intendedCount,
            ResourceCreationStrategy<TResource> resourceCreationStrategy,
            int maximumEnforcerCommandsRunningCount)
        {
            if (resourceCollection == null) throw new ArgumentNullException("resourceCollection");
            if (commandProcessor == null) throw new ArgumentNullException("commandProcessor");
            if (resourceCreationStrategy == null) throw new ArgumentNullException("resourceCreationStrategy");

            if (intendedCount <= 0)
            {
                throw new ArgumentOutOfRangeException("intendedCount", Resources.ExpectingNumberGreaterThanZero);
            }

            if (maximumEnforcerCommandsRunningCount < 1)
            {
                throw new ArgumentOutOfRangeException("maximumEnforcerCommandsRunningCount", Resources.ExpectingNumberGreaterThanZero);
            }

            _resourceCollection = resourceCollection;
            _resourceCreationStrategy = resourceCreationStrategy;
            _intendedCount = intendedCount;
            _commandProcessor = commandProcessor;

            _runningCommandsSemaphore = new Semaphore(maximumEnforcerCommandsRunningCount, maximumEnforcerCommandsRunningCount);
        }

        /// <summary>
        /// </summary>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="ObjectDisposedException"></exception>
        public void Start()
        {
            _disposableCookie.ThrowIfDisposed();

            if (_running)
            {
                throw new InvalidOperationException(Resources.CannotStartMonitorThatIsRunning);
            }

            _resourceCollection.ItemCountDown += OnItemCountDown;

            _running = true;

            QueueAddingItems();
        }

        private void OnItemCountDown(object sender, EventArgs e)
        {
            if (_resourceCollection.Count >= _intendedCount)
            {
                return;
            }

            QueueAddingItems();
        }

        private void QueueAddingItems()
        {
            Command command =
                    new AddItemsUntilCountReachedCommand<TResource>(
                        _resourceCollection,
                        _intendedCount,
                        _commandCancellationToken,
                        _resourceCreationStrategy,
                        _runningCommandsSemaphore);

            _commandProcessor.RegisterCommandWaitingForHandleSignal(_runningCommandsSemaphore, command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="ObjectDisposedException"></exception>
        public void Stop()
        {
            _disposableCookie.ThrowIfDisposed();

            if (!_running)
            {
                throw new InvalidOperationException(Resources.CannotStopMonitorThatIsStopped);
            }

            _resourceCollection.ItemCountDown -= OnItemCountDown;

            _commandCancellationToken.Cancel();

            _running = false;
        }

        protected virtual void Dispose(bool calledFromFinalizer)
        {
            if (_disposableCookie.IsDisposed)
            {
                return;
            }

            if (_running)
            {
                Stop();
            }

            _disposableCookie.MarkAsDisposed();
        }

        public void Dispose()
        {
            this.Dispose(false);
        }
    }
}